#include "lv_pcfs.h"

#ifdef USE_LV_PCFS
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <SDL2/SDL.h>
#include "lv_fs.h"

typedef SDL_RWops *pc_file_t;
typedef DIR *pc_dir_t;

static int pcfs_inited = 0;

/**
 * Open a file from the PC
 * @param file_p pointer to a FILE* variable
 * @param fn name of the file.
 * @param mode element of 'fs_mode_t' enum or its 'OR' connection (e.g. FS_MODE_WR | FS_MODE_RD)
 * @return LV_FS_RES_OK: no error, the file is opened
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t pcfs_open(lv_fs_drv_t * drv, void * file_p, const char * fn, lv_fs_mode_t mode)
{
    errno = 0;

    const char * flags = "";

    if (mode == LV_FS_MODE_WR) flags = "wb";
    else if (mode == LV_FS_MODE_RD) flags = "rb";
    else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = "w+b";
    else if (mode == (LV_FS_MODE_RD | LV_FS_MODE_APPEND)) flags = "r+b";
    else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_APPEND)) flags = "a+b";
    else flags = "w+b";

    /*Make the path relative to the current directory (the projects root folder)*/
    char path[LV_PCFS_MAX_FILE_NAME];
    sprintf(path, "%s%s", pcfs_get_exec_path(), fn);

    pc_file_t f = SDL_RWFromFile(path, flags);
    if ((long int)f == 0)
        return LV_FS_RES_UNKNOWN;
    else {
        SDL_RWseek(f, 0, RW_SEEK_SET);

        /* 'file_p' is pointer to a file descriptor and
         * we need to store our file descriptor here*/
        pc_file_t * fp = file_p;        /*Just avoid the confusing casings*/
        *fp = f;
    }

    return LV_FS_RES_OK;
}

/**
 * Close an opened file
 * @param file_p pointer to a FILE* variable. (opened with lv_ufs_open)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_close(lv_fs_drv_t * drv, void * file_p)
{
    pc_file_t * fp = file_p;        /*Just avoid the confusing casings*/
    SDL_RWclose(*fp);
    return LV_FS_RES_OK;
}

/**
 * Read data from an opened file
 * @param file_p pointer to a FILE variable.
 * @param buf pointer to a memory block where to store the read data
 * @param btr number of Bytes To Read
 * @param br the real number of read bytes (Byte Read)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{
    pc_file_t * fp = file_p;        /*Just avoid the confusing casings*/

    *br = SDL_RWread(*fp, buf, 1, btr);

    return LV_FS_RES_OK;
}

/**
 * Set the read write pointer. Also expand the file size if necessary.
 * @param file_p pointer to a FILE* variable. (opened with lv_ufs_open )
 * @param pos the new position of read write pointer
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos)
{
    pc_file_t *fp = file_p;        /*Just avoid the confusing casings*/

    SDL_RWseek(*fp, pos, RW_SEEK_SET);

    return LV_FS_RES_OK;
}

/**
 * Give the position of the read write pointer
 * @param file_p pointer to a FILE* variable.
 * @param pos_p pointer to to store the result
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
    pc_file_t *fp = file_p;        /*Just avoid the confusing casings*/

    *pos_p = SDL_RWtell(*fp);

    return LV_FS_RES_OK;
}

static lv_fs_res_t pcfs_size(struct _lv_fs_drv_t * drv, void * file_p, uint32_t * size_p)
{
    uint32_t cur_pos;
    pc_file_t *fp = file_p;        /*Just avoid the confusing casings*/

    cur_pos = SDL_RWtell(*fp);
    SDL_RWseek(*fp, 0, RW_SEEK_END);
    *size_p = SDL_RWtell(*fp);
    SDL_RWseek(*fp, cur_pos, RW_SEEK_SET);
    return LV_FS_RES_OK;
}

static lv_fs_res_t pcfs_write (lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw)
{
    pc_file_t * fp = file_p;        /*Just avoid the confusing casings*/

    *bw = SDL_RWwrite(*fp, buf, 1, btw);

    return LV_FS_RES_OK;
}

static bool pcfs_ready(lv_fs_drv_t * drv)
{
    return (pcfs_inited == 1);
}

static lv_fs_res_t pcfs_remove(lv_fs_drv_t * drv, const char * fn)
{
    char path[LV_PCFS_MAX_FILE_NAME];

    sprintf(path, "%s%s", pcfs_get_exec_path(), fn);

    if( remove(path) == 0 )
        return LV_RES_OK;
    else
        return LV_RES_INV;
}

/**
 * Initialize a lv_ufs_read_dir_t variable to directory reading
 * @param rddir_p pointer to a 'ufs_dir_t' variable
 * @param path uFS doesn't support folders so it has to be ""
 * @return LV_FS_RES_OK or any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_dir_open(lv_fs_drv_t *drv, void *rddir_p, const char *path)
{
    pc_dir_t open_dir;
    pc_dir_t *pc_dir = rddir_p;

    open_dir = opendir(path);

    if (open_dir == NULL) {
        return LV_FS_RES_NOT_EX;       /*Must be "" */
    } else {
        *pc_dir = open_dir;
        return LV_FS_RES_OK;
    }
}

/**
 * Read the next file name
 * @param dir_p pointer to an initialized 'ufs_dir_t' variable
 * @param fn pointer to buffer to sore the file name
 * @return LV_FS_RES_OK or any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_dir_read(lv_fs_drv_t *drv, void *rddir_p, char *fn)
{
    pc_dir_t *pc_dir = rddir_p;
    struct dirent *ent;

    ent = readdir(*pc_dir);
    if (ent != NULL) {
        strcpy(fn, ent->d_name);
        return LV_FS_RES_OK;
    } else {
        return LV_FS_RES_NOT_EX;
    }

    return LV_FS_RES_OK;
}

/**
 * Close the directory reading
 * @param rddir_p pointer to an initialized 'ufs_dir_t' variable
 * @return LV_FS_RES_OK or any error from lv__fs_res_t enum
 */
static lv_fs_res_t pcfs_dir_close(lv_fs_drv_t *drv, void *rddir_p)
{
    pc_dir_t *pc_dir = rddir_p;

    closedir(*pc_dir);

    return LV_FS_RES_OK;
}

int pcfs_init(void)
{
    lv_fs_drv_t pcfs_drv;

    if (pcfs_inited) {
        printf("pcfs has been inited\n");
        return 0;
    }
    memset(&pcfs_drv, 0, sizeof(lv_fs_drv_t));

    pcfs_drv.file_size = sizeof(pc_file_t);
    pcfs_drv.rddir_size = sizeof(pc_dir_t);
    pcfs_drv.open_cb = pcfs_open;
    pcfs_drv.close_cb = pcfs_close;
    pcfs_drv.read_cb = pcfs_read;
    pcfs_drv.write_cb = pcfs_write;
    pcfs_drv.seek_cb = pcfs_seek;
    pcfs_drv.tell_cb = pcfs_tell;
    pcfs_drv.ready_cb = pcfs_ready;
    pcfs_drv.size_cb   = pcfs_size;
    pcfs_drv.remove_cb = pcfs_remove;

    lv_fs_drv_register(&pcfs_drv);

    pcfs_drv.letter = 'C';  //for flash
    lv_fs_drv_register(&pcfs_drv);

    pcfs_drv.letter = 'D';  //for sdcard
    /* only sd card fs support dir */
    pcfs_drv.dir_open_cb = pcfs_dir_open;
    pcfs_drv.dir_read_cb = pcfs_dir_read;
    pcfs_drv.dir_close_cb = pcfs_dir_close;
    lv_fs_drv_register(&pcfs_drv);

    pcfs_inited = 1;

    return 0;
}

char *pcfs_get_exec_path(void)
{
    static char *exec_path;

    if (!exec_path) {
        exec_path = SDL_GetBasePath();
    }

    return exec_path;
}

#endif
